﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace Bing.Image
{
    /// <summary>
    /// Interaction logic for ImageWindow.xaml
    /// </summary>
    public partial class ImageWindow
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ImageWindow"/> class.
        /// </summary>
        public ImageWindow()
        {
            InitializeComponent();
            _imageResults = new ObservableCollection<ImageResult>();

            txtSearch.SearchImage = getImage(smallSearchIconUrl());
            imgBingLogo.Source = getImage(logoBigDarkUrl());
            
        }

        #region Properties

        private readonly BrandAssets _brandAssets = new BrandAssets();

        private ObservableCollection<ImageResult> _imageResults;
        /// <summary>
        /// Used to hold all of the images loaded
        /// </summary>
        public ObservableCollection<ImageResult> ImageResults
        {
            get { return _imageResults; }
            set { _imageResults = value;}
        }

        private UInt32 _pageNumber;

        private static BitmapImage getImage(string url)
        {
            return new BitmapImage(new Uri(url));
        }

        private string smallSearchIconUrl()
        {
            if (!_brandAssets.IsLoaded)
                _brandAssets.Load();

            return !_brandAssets.ImageAssets.ContainsKey("Icon16")
                       ? String.Empty
                       : _brandAssets.ImageAssets["Icon16"].Url;
        }

        private string logoBigDarkUrl()
        {
            if (!_brandAssets.IsLoaded)
                _brandAssets.Load();

            return !_brandAssets.ImageAssets.ContainsKey("LogoBigDark")
                       ? String.Empty
                       : _brandAssets.ImageAssets["LogoBigDark"].Url;
        }

        #endregion Properties

        #region Private Methods

        /// <summary>
        /// Gets the next page of images.
        /// </summary>
        private void getNextPage()
        {
            txtSearch.IsEnabled = false;
            getImages(_pageNumber);
            _pageNumber++;
            txtSearch.IsEnabled = true;
        }

        /// <summary>
        /// Updates the status.
        /// </summary>
        /// <param name="text">The text.</param>
        private void updateStatus(string text)
        {
            lblStatus.Content = text;
        }

        /// <summary>
        /// Updates the status.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="options">The options.</param>
        private void updateStatus(string text, params object[] options)
        {
            string temp = string.Format(text, options);
            updateStatus(temp);
        }

        /// <summary>
        /// Gets the images.
        /// </summary>
        /// <param name="pageNumber">The page number to load.</param>
        private void getImages(uint pageNumber)
        {
            updateStatus("Getting images for page {0}.", pageNumber);

            var appId = txtAppId.Text.Trim();
            var query = txtSearch.Text.Trim();
            var strImageCount = txtPageCount.Text.Trim();
            UInt32 imageCount;
            
            if (string.IsNullOrEmpty(query))
            {
                imgWordGood.Source = null;
                updateStatus("The query string was empty.");
                return;
            }

            if (!uint.TryParse(strImageCount, out imageCount))
            {
                updateImage(false);
                updateStatus("Invalid image count. The value must be between 1 and 50.");
                MessageBox.Show("The Image Count must be a valid number", "Data validation error", MessageBoxButton.OK,
                                MessageBoxImage.Error);
                return;
            }

            SearchRequest searchRequest = new SearchRequest { AppId = appId, Query = query, Market = "en-US" };
            ImageRequest imageRequest = new ImageRequest();
            imageRequest.Filters = buildFilterArray();
            imageRequest.Count = imageCount;
            imageRequest.Offset = (imageCount * pageNumber);
            ImageResponse response = API.Image(searchRequest, imageRequest);

            if (response.Errors.Count > 0)
            {
                // Display Errors
                updateImage(false);
                MessageBox.Show(bingErrorMessage(response.Errors), "Bing.API Error", MessageBoxButton.OK,MessageBoxImage.Error);
                updateStatus("The Bing.API returned {0} error(s).", response.Errors.Count);
                return;
            }

            response.Results.ForEach(a => ImageResults.Add(a));
            updateImage(true);
            updateStatus("Ready");
        }

        /// <summary>
        /// Builds a string with the error messages returned from Bing.
        /// </summary>
        /// <param name="errors">The <see cref="IEnumerable{T}"/> collection of <see cref="Bing.Error" />.</param>
        /// <returns></returns>
        private static string bingErrorMessage(IEnumerable<Error> errors)
        {
            StringBuilder text = new StringBuilder();
            int counter = 1;
            foreach (Error error in errors)
            {
                text.AppendFormat("Error #{0}.\r\n", counter);
                text.AppendFormat("\tMessage: {0}\r\n", error.Message);
                text.AppendFormat("\tCode: {0}\r\n", error.Code);
                text.AppendFormat("\tHelpUrl: {0}\r\n", error.HelpUrl);
                text.AppendFormat("\tParameter: {0}\r\n", error.Parameter);
                text.AppendFormat("\tValue: {0}\r\n", error.Value);
                counter++;
            }

            return text.ToString();
        }

        /// <summary>
        /// Updates the image.
        /// </summary>
        /// <param name="isOk">Displays the OK image if true or the X if false.</param>
        private void updateImage (bool isOk)
        {
            imgWordGood.Source = isOk
                                     ? new BitmapImage(new Uri("pack://application:,,,/Images/okay.png"))
                                     : new BitmapImage(new Uri("pack://application:,,,/Images/error_x.png"));
        }

        /// <summary>
        /// Builds an array of Filters for the Request
        /// </summary>
        /// <returns>A string[] of filters</returns>
        private string[] buildFilterArray()
        {
            List<string> filters = new List<string>();
            
            // Size
            if (optSizeSmall.IsChecked.IsChecked()) filters.Add("Size:Small");
            if (optSizeMedium.IsChecked.IsChecked()) filters.Add("Size:Medium");
            if (optSizeLarge.IsChecked.IsChecked()) filters.Add("Size:Large");
            if (chkSizeHeight.IsChecked.IsChecked())
            {
                string temp = string.Format("Size:Height:{0}", txtSizeHeight.Text.Trim());
                filters.Add(temp);
            }
            if (chkSizeWidth.IsChecked.IsChecked())
            {
                string temp = string.Format("Size:Width:{0}", txtSizeWidth.Text.Trim());
                filters.Add(temp);
            }

            // aspect
            if (optAspectSquare.IsChecked.IsChecked()) filters.Add("Aspect:Square");
            if (optAspectWide.IsChecked.IsChecked()) filters.Add("Aspect:Wide");
            if (optAspectTall.IsChecked.IsChecked()) filters.Add("Aspect:Tall");

            // Color
            if (optColorColor.IsChecked.IsChecked()) filters.Add("Color:Color");
            if (optColorMonochrome.IsChecked.IsChecked()) filters.Add("Color:Monochrome");


            // Style
            if (optStylePhoto.IsChecked.IsChecked()) filters.Add("Style:Photo");
            if (optStyleGraphics.IsChecked.IsChecked()) filters.Add("Style:Graphics");

            // Face
            if (optFaceFace.IsChecked.IsChecked()) filters.Add("Face:Face");
            if (optFacePortrait.IsChecked.IsChecked()) filters.Add("Face:Portrait");
            if (optFaceOther.IsChecked.IsChecked()) filters.Add("Face:Other");

            return filters.ToArray();
        }

        
        #endregion Private Methods

        #region Events

        private void scrollViewerScrollChanged(object sender, System.Windows.Controls.ScrollChangedEventArgs e)
        {
            double percentScrolled = (e.VerticalOffset / (e.ExtentHeight - e.ViewportHeight)) * 100;

            //string temp = string.Format("Visible: {0}, Total Items:{1}, At:{2}. {3}", e.ViewportHeight, e.ExtentHeight,
            //                            e.VerticalOffset, percentScrolled);
            //System.Diagnostics.Debug.Print(temp);

            if (percentScrolled > 85)
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate
                                                                                 {
                                               updateStatus("Getting images.");
                                               getNextPage();
                                               updateStatus("Ready.");
                                           }) );
            }
        }

        private void hyperlink_Click(object sender, RoutedEventArgs e)
        {
            if (sender.GetType() != typeof(Hyperlink)) return;

            string navigateUri = ((Hyperlink)sender).NavigateUri.ToString();
            // if the URI somehow came from an untrusted source, make sure to
            // validate it before calling Process.Start(), e.g. check to see
            // the scheme is HTTP, etc.
            Process.Start(new ProcessStartInfo(navigateUri));
            e.Handled = true;
        }

        private void searchTextBox_Search(object sender, RoutedEventArgs e)
        {
            getNextPage();
        }

        #endregion Events

        
    }
}